#!/usr/local/bin/python3
# -*- coding: utf-8 -*-

"""
@File    : utils.py
@Author  : Link
@Time    : 2022/5/14 12:53
@Mark    : 
"""
import numpy as np
import pyqtgraph as pg
from PySide2 import QtCore
from PySide2.QtCore import Signal
from pyqtgraph import ViewBox, Point, BarGraphItem, InfiniteLine, ScatterPlotItem, mkPen, colormap, ImageItem, \
    GraphicsLayoutWidget, ColorBarItem, LabelItem
import pyqtgraph.functions as fn

from data_core.li import Li

"""
TODO: rota 的含义
    0b 0           0           0           0
       ⬇           ⬇           ⬇           ⬇
    数据在X       数据在Y      标记线在X   标记线在Y
    
例如 柱状图 self.rota = 0b0101
    它的数据和标记线都是以Y来做为参考的
"""


def graph_range_signal(func):
    def wrapper(ctx, *args, **kwargs):
        try:
            ctx.pw.sigRangeChanged.disconnect()
        except RuntimeError:
            pass
        res = func(ctx, *args, **kwargs)
        ctx.front_data_min, ctx.front_data_max = ctx.bottom_axis.range
        ctx.pw.sigRangeChanged.connect(ctx.set_range_data_to_chart)
        return res

    return wrapper


def graph_range_signal(func):
    def wrapper(ctx, *args, **kwargs):
        try:
            ctx.pw.sigRangeChanged.disconnect()
        except RuntimeError:
            pass
        res = func(ctx, *args, **kwargs)
        if ctx.rota == 0b0101:
            ctx.front_data_min, ctx.front_data_max = ctx.left_axis.range
        if ctx.rota == 0b0110:
            ctx.front_data_min, ctx.front_data_max = ctx.left_axis.range
        if ctx.rota == 0b1001:
            ctx.front_data_min, ctx.front_data_max = ctx.bottom_axis.range
        if ctx.rota == 0b11001:
            ctx.front_data_min, ctx.front_data_max = ctx.bottom_axis.range
        if ctx.rota == 0b1010:
            ctx.front_data_min, ctx.front_data_max = ctx.bottom_axis.range
        ctx.pw.sigRangeChanged.connect(ctx.set_range_data_to_chart)
        return res

    return wrapper


class PlotWidget(pg.PlotWidget):
    # sig_signal = Signal(str)

    def keyPressEvent(self, ev):
        print(ev)
        super().keyPressEvent(ev)

    def keyReleaseEvent(self, ev):
        print(ev)
        super().keyPressEvent(ev)

    # def resizeEvent(self, ev):
    #     try:
    #         self.sigRangeChanged.disconnect()
    #     except RuntimeError:
    #         pass
    #     super(PlotWidget, self).resizeEvent(ev=ev)
    #     self.sig_signal.emit()


class CustomViewBox(pg.ViewBox):
    """
    重写了ViewBox中 选取区间的功能
    """
    select_signal = QtCore.Signal(object)

    def __init__(self, *args, **kwds):
        kwds['enableMenu'] = False
        pg.ViewBox.__init__(self, *args, **kwds)
        self.setMouseMode(self.RectMode)

    def mouseClickEvent(self, ev):
        """

        :param ev:
        :return:
        """
        if ev.button() == QtCore.Qt.MouseButton.RightButton:
            ev.ignore()
            self.select_signal.emit(None)
            # self.autoRange()

    def wheelEvent(self, ev, axis=None):
        """

        :param ev:
        :param axis:
        :return:
        """
        if axis is None:
            ev.ignore()
            return
        super().wheelEvent(ev, axis)

    def mouseDragEvent(self, ev, axis=None):
        """
        重写鼠标拽取绘制事件
        :param ev:
        :param axis:
        :return:
        """
        if axis is not None and ev.button() in [QtCore.Qt.MouseButton.RightButton,
                                                QtCore.Qt.MouseButton.MiddleButton]:
            ev.ignore()
        else:
            self._mouseDragEvent(ev, axis=axis)

    def _mouseDragEvent(self, ev, axis=None):
        ## if axis is specified, event will only affect that axis.
        ev.accept()  ## we accept all buttons

        pos = ev.scenePos()
        dif = pos - ev.lastScenePos()
        dif = dif * -1

        ## Ignore axes if mouse is disabled
        mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float64)
        mask = mouseEnabled.copy()
        if axis is not None:
            mask[1 - axis] = 0.0

        ## Scale or translate based on mouse button
        if ev.button() in [QtCore.Qt.MouseButton.LeftButton, QtCore.Qt.MouseButton.MiddleButton]:
            if self.state['mouseMode'] == ViewBox.RectMode and axis is None:
                if ev.isFinish():  ## This is the final move in the drag; change the view scale now
                    # print "finish"
                    self.rbScaleBox.hide()
                    ax = QtCore.QRectF(Point(ev.buttonDownScenePos(ev.button())), Point(pos))
                    ax = self.childGroup.mapRectFromScene(ax)
                    # self.showAxRect(ax)
                    self.select_signal.emit(ax)
                    self.axHistoryPointer += 1
                    self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax]
                else:
                    ## update shape of scale box
                    self.updateScaleBox(ev.buttonDownScenePos(), ev.scenePos())
            else:
                tr = self.childGroup.transform()
                tr = fn.invertQTransform(tr)
                tr = tr.map(dif * mask) - tr.map(Point(0, 0))

                x = tr.x() if mask[0] == 1 else None
                y = tr.y() if mask[1] == 1 else None

                self._resetTarget()
                if x is not None or y is not None:
                    self.translateBy(x=x, y=y)
                self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
        # elif ev.button() & QtCore.Qt.MouseButton.RightButton:
        #     # print "vb.rightDrag"
        #     if self.state['aspectLocked'] is not False:
        #         mask[0] = 0
        #
        #     dif = ev.screenPos() - ev.lastScreenPos()
        #     dif = np.array([dif.x(), dif.y()])
        #     dif[0] *= -1
        #     s = ((mask * 0.02) + 1) ** dif
        #
        #     tr = self.childGroup.transform()
        #     tr = fn.invertQTransform(tr)
        #
        #     x = s[0] if mouseEnabled[0] == 1 else None
        #     y = s[1] if mouseEnabled[1] == 1 else None
        #
        #     center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.MouseButton.RightButton)))
        #     self._resetTarget()
        #     self.scaleBy(x=x, y=y, center=center)
        #     self.sigRangeChangedManually.emit(self.state['mouseEnabled'])


class Mixin:
    rota = 0b0000  # data x,data y,line x, line y

    def resize(self, w, h):
        self.pw.resize(w, h)

    def widget(self, width: int):
        self.pw.setMaximumWidth(width)
        return self.pw

    @graph_range_signal
    def pw_show(self):
        self.pw.show()

    def set_bins(self, n):
        self.bins_num = n

    def set_title(self, title: str = "PlotItem with CustomAxisItem, CustomViewBox"):
        self.pw.plotItem.setTitle(title)

    def set_li(self, li: Li):
        """
        基本li这个类生成后就不会变化了
        :param li:
        :return:
        """
        self.li = li
        self.li.front_df_signal.connect(self.set_front_df_chart)
        self.li.chart_df_signal.connect(self.set_chart_df_chart)
        self.li.full_chart_signal.connect(self.full_change_chart)

    # def set_data(self, key: str, data_min=0, data_max=10):
    #     if self.li is None:
    #         raise Exception("first set li")
    #     self.key = key
    #     self.set_title(key)
    #     self.front_data_min, self.front_data_max = data_min, data_max
    #     if data_min is None:
    #         self.front_data_min = self.li.front_df[self.key].min()
    #     if data_max is None:
    #         self.front_data_max = self.li.front_df[self.key].max()
    #     self.left_axis.setRange(self.front_data_min, self.front_data_max)

    def set_range(self, lo, hi):
        if self.rota == 0b11001:
            self.vb.setYRange(lo, hi)
        if self.rota == 0b1001:
            self.vb.setXRange(lo, hi)
        if self.rota == 0b1010:
            self.vb.setXRange(lo, hi)
        if self.rota == 0b0101:
            self.vb.setYRange(lo, hi)
        if self.rota == 0b0110:
            self.vb.setYRange(lo, hi)
        self.front_data_min, self.front_data_max = lo, hi

    def set_line_self(self):
        cpk_dict = getattr(self.li, 'cpk_dict', None)
        if cpk_dict is None:
            return
        arg = cpk_dict.get(self.key, None)
        if not arg:
            return
        self.unit = arg["UNITS"]

        l_limit = arg["LO_LIMIT"]
        h_limit = arg["HI_LIMIT"]
        if self.li.limit == 0:
            l_limit = arg["LO_LIMIT"]
            if isinstance(arg["LO_LIMIT_TYPE"], float):
                l_limit = arg["Min"]
            h_limit = arg["HI_LIMIT"]
            if isinstance(arg["HI_LIMIT_TYPE"], float):
                h_limit = arg["Max"]
        if self.li.limit == 1:
            l_limit = arg["Min"]
            h_limit = arg["Max"]
        if self.li.limit == 3:
            rig_x = arg["Stdev"] * self.li.rig
            l_limit = arg["Average"] - rig_x
            h_limit = arg["Average"] + rig_x

        self.set_lines(
            HI_LIMIT=h_limit,
            LO_LIMIT=l_limit,
            Average=arg["Average"],
        )

    @graph_range_signal
    def set_range_self(self):
        """
        这里不要影响到线性图了
        :return:
        """
        if self.li.limit == 2:
            return
        cpk_dict = getattr(self.li, 'cpk_dict', None)
        if cpk_dict is None:
            return
        arg = cpk_dict.get(self.key, None)
        if not arg:
            return
        if self.li.limit == 1:
            self.front_data_min, self.front_data_max = arg["Min"], arg["Max"]
        if self.li.limit == 0:
            l_limit = arg["LO_LIMIT"]
            if isinstance(arg["LO_LIMIT_TYPE"], float):
                l_limit = arg["Min"]
            h_limit = arg["HI_LIMIT"]
            if isinstance(arg["HI_LIMIT_TYPE"], float):
                h_limit = arg["Max"]
            step = arg["Stdev"] * 2
            self.front_data_min, self.front_data_max = l_limit - step, h_limit + step
        if self.li.limit == 3:
            step = arg["Stdev"] * self.li.rig
            self.front_data_min, self.front_data_max = arg["Average"] - step, arg["Average"] + step
        self.set_range(self.front_data_min, self.front_data_max)

    @graph_range_signal
    def set_lines(self, **kwargs):
        """
        传入class
        :param kwargs:
        :return:
        """
        if self.lines is None:
            self.lines = {}
        for key, item in kwargs.items():
            if key in self.lines:
                inf = self.lines[key]  # type:InfiniteLine
                inf.setPos(item)
            else:
                ang = 0
                if self.rota == 0b1010:
                    ang = 90
                if self.rota == 0b0110:
                    ang = 90
                inf = InfiniteLine(movable=False, angle=ang, pen=(255, 0, 0), hoverPen=(0, 200, 0),
                                   label=key + '={value:0.5f}' + self.unit,
                                   labelOpts={'color': (255, 0, 0), 'movable': True, 'fill': (0, 0, 200, 100),
                                              'position': 0.1, })
                inf.setPos(item)
                self.pw.addItem(inf)

    def set_range_data_to_chart(self, a, ax):
        if self.li is None:
            return
        sig = 1  # 1 是 X, 0 是Y 的变化
        if self.rota == 0b1001:
            sig = 0
        if self.rota == 0b11001:
            sig = 0
        if self.rota == 0b1010:
            sig = 0
        percent = (ax[sig][1] - ax[sig][0]) / (self.front_data_max - self.front_data_min)
        if 0.95 < percent < 1.05:
            """ 性能会好些,体验会差点 """
            return
        self.front_data_min, self.front_data_max = ax[sig]  # x轴变化
        self.set_front_df_chart()

    def close_s(self):
        try:
            self.li.front_df_signal.disconnect(self.set_front_df_chart)
        except:
            pass
        try:
            self.li.chart_df_signal.disconnect(self.set_chart_df_chart)
        except:
            pass
        try:
            self.li.full_chart_signal.disconnect(self.full_change_chart)
        except:
            pass
        self.li = None
